/*	
 *      Krawsk :main.c example kernel module to send and receive raw packets using kernel raw sockets.
 *  
 *  
 *      Author: SaeedM <saeed.tx@gmail.com> , Aug-2011
 *
 * 
 * 	This program is free software; you can redistribute it and/or
 *      modify it under the terms of the GNU General Public License
 *      as published by the Free Software Foundation; either version
 *      2 of the License, or (at your option) any later version.
 * 
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/cdev.h>
//#include <linux/slab.h>
#include <asm/uaccess.h>

#include <linux/sched.h>
#include <linux/proc_fs.h>
#include <linux/moduleparam.h>

#include <linux/if_ether.h>
#include <linux/workqueue.h> //for testing send work


#include "kraweth_data.h"
#include "krawsock.h"


MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("SaeedM");
MODULE_DESCRIPTION("Kernel RAW Packet send/recv module");
MODULE_VERSION("0.1-test");
MODULE_ALIAS("krawsk");

static int buffsize=1024;
static char* ifname="eth0";

module_param(ifname, charp, S_IRUGO);
module_param(buffsize, int, S_IRUGO);

struct workqueue_struct *wq;// = create_workqueue("send_queue");

struct krudp_dev {
	void* data;
	struct cdev cdev;
	unsigned int buffsize;
	unsigned int tx;
	unsigned int rx;
	unsigned int tx_err;
	struct socket* sk;
	struct net_device * net_dev; 
} devs;

static dev_t devt ;


static unsigned int major = 60;
static unsigned int minor = 0;


int 	mod_open(struct inode *inode, struct file *filp);

int 	mod_release(struct inode *inode, struct file *filp);

ssize_t mod_read(struct file *filp, char __user *buf, size_t count,loff_t *f_pos);

ssize_t mod_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos);

struct file_operations my_fops = {
	.owner =	THIS_MODULE,
//	.llseek =	my_llseek,
	.read =		mod_read,
	.write =	mod_write,	
//	.ioctl =	my_ioctl,	
	.open =		mod_open,
	.release = 	mod_release,
};


void dump_buff(void* buff,int size){
	u_int8_t * cbuff = (u_int8_t*)buff;
	int i=0;
	for (i=0;i < size && i < 40;i+=1){
		printk("%02x:",cbuff[i]);	
	}
	if (size > 40) printk("...\n");
	else printk("\n");
}

int mod_open(struct inode *inode, struct file *filp)
{
	struct krudp_dev *dev; /* device information */
	dev = container_of(inode->i_cdev, struct krudp_dev, cdev);
	filp->private_data = dev; /* for other methods */
	KLOG("Device is being opened by %s [%d]\n",current->comm,current->pid);
	KLOG("OPEN SUCCESS\n");
	return 0;
	/* success */
}

int mod_release(struct inode *inode, struct file *filp)
{
	KLOG("CLOSE SUCCESS\n");
	return 0;
}

ssize_t mod_read(struct file *filp, char __user *buf, size_t count,loff_t *f_pos)
{
	ssize_t retval = -ENOMEM,total; 
//        struct krudp_dev *dev = filp->private_data;
	total = krecv(devs.sk,devs.data,buffsize, 0 /*flags*/);
	if ( total < 0 ) {
		KLOG("krecv Failed :  %d\n",total);
		return total;
	}
	devs.rx++;
	retval = 0;
	KLOG("Got message size :  %d\n",total);
	dump_buff(devs.data,total);
	//total = copy_to_user (buf,devs.data,total);
	//KLOG("copied %d , left %d\n",total,retval);
	return 0;
}

//void (*send_packet_work)(void)
unsigned char packet[1000+ETH_HLEN];


void send_packet_work(struct work_struct *work){
	int len=500;
	int total = -1;
	KLOG("sending packet  %d\n",len);	
	total = ksend(devs.sk,(void *)&packet, len+ETH_HLEN,0,devs.net_dev->ifindex);
	if (total < 0){
		KLOG("Failed to send packet : %d\n",total);	
		devs.tx_err++;
		//return total;
	}	
}

struct work_struct work;

ssize_t mod_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
{
	//struct krudp_dev *dev = filp->private_data;
	size_t total = -1; /* value used in "goto out" statements */
	int len = 500;//count


	struct ethhdr hdr =  {
		.h_dest = {0x00,0x11,0x33,0x44,0x55,0xff},
		.h_source = {0x00,0x11,0x22,0x33,0x44,0x55},
		.h_proto = htons(len)
	};
	memcpy(&packet,&hdr,ETH_HLEN);
	INIT_WORK(&work,send_packet_work);

	KLOG("send work  %d\n",len);	
	queue_work(wq, &work );
/*	total = ksend(devs.sk,(void *)&packet, len+ETH_HLEN,0,devs.net_dev->ifindex);
	if (total < 0){
		KLOG("Failed to send packet : %d\n",total);	
		devs.tx_err++;
		return total;
	}
	devs.tx++;
	dump_buff((void*)&packet,len+ETH_HLEN);
*/	
	total = 500;
	KLOG("sent : %d\n",total);	
	return total;
}

static void setup_cdev(struct krudp_dev *dev, int index)
{
	int err, devno = MKDEV(major, minor + index);
	cdev_init(&dev->cdev, &my_fops);
	dev->cdev.owner = THIS_MODULE;
	dev->cdev.ops = &my_fops;
	err = cdev_add (&dev->cdev, devno, 1);
	/* Fail gracefully if need be */
	if (err)
		KLOG("Error %d adding %s%d", err,MOD_NAME ,index);
}

static int my_init(void)
{
	//struct sockaddr sa;
	int err = -EINVAL;
	//struct ifreq ifr;
	KLOG( "init ...\n");
	if (alloc_chrdev_region(&devt, 0,1, MOD_NAME)) {	
		KLOG("failed to allocate minor \n");
		return -EINVAL;
	}
        	
	major = MAJOR(devt);
	minor = MINOR(devt);
	KLOG("my major = %d minor = %d\n",major,minor);

       	KLOG("ifname = %s buffsize = %d\n",ifname,buffsize);
	devs.data = kmalloc(buffsize*sizeof(char*),GFP_KERNEL);
	devs.buffsize = buffsize;
	devs.tx = 0;
	devs.rx = 0;
	devs.tx_err = 0;	
	if (!devs.data){
		KLOG("failed to allocate buffer\n");
		unregister_chrdev_region(devt, 1);
		return  -ENOMEM;
	}
	wq = create_workqueue("send_queue");//create_singlethread_workqueue(const char *name);
	if(!wq) {
		KLOG("failed to create work queue \n");
		unregister_chrdev_region(devt, 1);
		return  -ENOMEM;
			
	}
	devs.net_dev = dev_get_by_name(&init_net,ifname);
        KLOG("bind kernel socket to %s : %d\n",ifname,devs.net_dev->ifindex);

	if( !(devs.sk = krsocket_open(ifname,devs.buffsize,devs.buffsize))){
		KLOG("Failed to initialize kernel raw socket\n");
		return err;
	}
		
	KLOG("creating cdev\n");
        setup_cdev(&devs,0);       
 	if (proc_create(MOD_NAME, 0, NULL, &my_fops) == NULL) {
 		KLOG("failed to allocate proc sysfs\n");
		cdev_del(&devs.cdev);
                unregister_chrdev_region(devt, 1);

                return  -ENOMEM;
	}
	KLOG("init [ OK ]\n");
	return 0;
}

static void my_exit(void)
{
	remove_proc_entry(MOD_NAME, NULL);
	KLOG("deleting cdev \n");
	cdev_del(&devs.cdev);
	if (devs.data)
		kfree(devs.data);
	flush_workqueue(wq);
	destroy_workqueue(wq );
	krsocket_close(devs.sk);	
	KLOG("unregister me Major %d\n",major);
        unregister_chrdev_region(devt, 1);	
	KLOG(" tx %d, rx %d , tx_err %d \n",devs.rx,devs.tx,devs.tx_err);
	KLOG("exit [ OK ]\n");
}


module_init(my_init);
module_exit(my_exit);



